package routower

import (
	"fmt"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"sync"
	"time"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gdao/roudao"
	"wzgames/internal/gloader"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/roulettep"
	"wzgames/internal/response"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/common/timingwheel"
	"wzgames/pkg/components/mysql"
	"wzgames/pkg/components/redis"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/stringutil"
)

// 新轮盘投注统计

type RouBetMgr struct {
	mu     sync.RWMutex
	pid    *actor.PID
	instId int64
	ds     *mysql.DS
	dao    *roudao.RouletteDao
	redisC *redis.RedisClient

	// first 3 bets 统计前3个投注
	fmap  map[uint64]bool
	farr  []BetU
	count int

	bets   []RouBet // 当前轮投注列表
	betpos int
	betKey string

	betCount int64

	t1 *timingwheel.Timer // 定时推送投注玩家给客户端
}

func newRouBetMgr(instId int64) *RouBetMgr {
	s := strconv.FormatInt(instId, 10)
	ab := &RouBetMgr{
		instId: instId,
		fmap:   make(map[uint64]bool),
		count:  0,
		betKey: constant.RouBetsNKey + "." + s,
		redisC: gloader.CommMgr().RedisPool(),
	}

	name := fmt.Sprintf("rou-ab-%s", strconv.FormatInt(instId, 10))
	props := actor.PropsFromProducer(func() actor.Actor { return ab })
	pid, err := actorservice.ActorSystem().Root.SpawnNamed(props, name)
	if err != nil {
		logger.Error("[RouTower] spawn roulette bet manager actor error", zap.Int64("InstId", instId), zap.Error(err))
		return nil
	}
	ab.pid = pid
	ab.ds = gloader.CommMgr().GetDBProxy(constant.MysqlOfRoulette)
	ab.dao = &roudao.RouletteDao{Driver: ab.ds.Ds()}

	return ab
}

func (ab *RouBetMgr) Pid() *actor.PID {
	return ab.pid
}

func (ab *RouBetMgr) Receive(ctx actor.Context) {
	switch m := ctx.Message().(type) {
	case *gamep.BetNote:
		switch m.Op {
		case constant.RouBetState: // 投注
			ab.processBetNote(m)
		}
	default:
	}
}

func (ab *RouBetMgr) Clean() {
	ab.mu.Lock()
	ab.fmap = make(map[uint64]bool)
	ab.farr = ab.farr[0:0]
	ab.count = 0
	ab.mu.Unlock()
}

// PrepareForNextRound 新一轮准备开始，清空旧数据
func (ab *RouBetMgr) PrepareForNextRound() {
	ab.StopBetsInfoTimer()
	ab.bets = ab.bets[0:0]
	ab.betpos = 0
	ab.betCount = 0
}

func (ab *RouBetMgr) StopBetsInfoTimer() {
	if ab.t1 != nil {
		ab.t1.Stop()
		ab.t1 = nil
	}
}

func (ab *RouBetMgr) processBetNote(data *gamep.BetNote) {
	defer logger.Recover()
	ab.mu.Lock()
	if ab.count < 3 { // 前3个投注的用于计算开奖结果
		if _, ok := ab.fmap[data.BetU.Uid]; !ok {
			ab.count++
			ab.fmap[data.BetU.Uid] = true
			ab.farr = append(ab.farr, BetU{
				AgtId:    data.BetU.AgtId,
				AgtKey:   data.BetU.AgtKey,
				Uid:      data.BetU.Uid,
				Uname:    data.BetU.Uname,
				Seed:     data.ClientSeed,
				Uhead:    data.BetU.Uhead,
				Currency: data.BetU.Currency,
			})
		}
	}
	ab.mu.Unlock()
	_bet, _ := stringutil.ToFloat64(data.BetStr, 0)
	ab.bets = append(ab.bets, RouBet{
		AgtId:    data.BetU.AgtId,
		Uid:      data.BetU.Uid,
		AgtKey:   data.BetU.AgtKey,
		Uname:    data.BetU.Uname,
		Uhead:    data.BetU.Uhead,
		Currency: data.BetU.Currency,
		BetAmt:   _bet,
		FreeBet:  data.FreeBet,
		Chips:    data.Chips,
	})
	ab.betCount++
	response.ReleaseBetNote(data)
}

func (ab *RouBetMgr) First3Bets() (*roulettep.First3Bets, string) {
	first3 := &roulettep.First3Bets{}
	var combineStr strings.Builder
	for _, nd := range ab.farr {
		first3.First3 = append(first3.First3, &gamep.BetUser{
			AgtId:    nd.AgtId,
			AgtKey:   nd.AgtKey,
			Uid:      nd.Uid,
			Uname:    nd.Uname,
			Seed:     nd.Seed,
			Uhead:    nd.Uhead,
			Currency: nd.Currency,
		})
		combineStr.WriteString(nd.Seed)
	}
	return first3, combineStr.String()
}

func (ab *RouBetMgr) RoundBetCount() int64 {
	return ab.betCount
}

// BetsNotify 批量投注播报
func (ab *RouBetMgr) BetsNotify() []byte {
	ret := &gamep.BetsNotify{Tp: constant.RouBetState}
	l := len(ab.bets)
	var c int
	var data []RouBet
	if l-ab.betpos >= 50 {
		c = 50
		end := ab.betpos + 50
		data = ab.bets[ab.betpos:end]
		ab.betpos = end
	} else {
		if ab.betpos < l {
			c = l - ab.betpos
			data = ab.bets[ab.betpos:l]
			ab.betpos = l
		}
	}

	if c > 0 {
		ret.Count = int32(l)
		xw := sfs.XWriter{}
		defer xw.Reset()
		xw.WriteUShort(uint16(2))
		xw.WriteStringS(sfs.CmdKey, constant.CmdUpdateCurrentBets)
		xw.WriteSfsObjectPre(sfs.ParamId, uint16(3))
		xw.WriteShortS(sfs.KeyCode, response.CSuccess)
		xw.WriteIntS(constant.KeyBetsCount, ret.Count)
		xw.WriteSfsArrayPre(constant.KeyBets, uint16(len(data)))
		for _, bt := range data {
			xw.WriteUByte(sfs.SfsObject)
			xw.WriteUShort(uint16(7))
			xw.WriteDoubleS(constant.KeyBet, bt.BetAmt)
			xw.WriteStringS(constant.KeyCurrency, bt.Currency)
			xw.WriteBoolS(constant.KeyIsFreeBet, bt.FreeBet)
			xw.WriteStringS(constant.KeyPlayerid, fmt.Sprintf("%v_%s", bt.Uid, bt.AgtKey))
			xw.WriteStringS(constant.KeyUsername, fmt.Sprintf("%s_%s", bt.AgtKey, bt.Uname))
			xw.WriteStringS(constant.KeyProfileImage, bt.Uhead)
			xw.WriteStringS(constant.KeyChips, bt.Chips)
		}
		ret.Data = append(ret.Data, xw.Data()...)
		b, err := proto.Marshal(ret)
		if err != nil {
			logger.Error("[RouTower] bets notify fail on marshal", zap.Int64("InstId", ab.instId), zap.Error(err))
			return nil
		}

		return b
	}

	return nil
}

func (ab *RouBetMgr) BroadcastBetsInfo(data interface{}) {
	betsInfo := ab.BetsNotify()
	if betsInfo != nil {
		_ = gloader.CommMgr().NatsC().Publish(ab.betKey, betsInfo)
	}

	ab.t1 = TimerAfter(time.Second, ab.BroadcastBetsInfo, nil)
}
